package monitor

import (
	"fmt"
	"github.com/ochinchina/supervisord/influx"
	"strconv"
	"strings"
	"time"

	"github.com/ochinchina/supervisord/config"
	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/load"
	"github.com/shirou/gopsutil/mem"
	"github.com/shirou/gopsutil/process"
	log "github.com/sirupsen/logrus"
)

type Obj struct {
	O interface{}
	e error
}

func newObj(o interface{}, ee error) Obj {
	return Obj{
		O: o,
		e: ee,
	}
}

var diskFlag int = 0

var pars []disk.PartitionStat

func InitMonitor() {
	v8, _ := disk.Partitions(false)
	pars = make([]disk.PartitionStat, len(v8))
	size := 0
	for _, par := range v8 {
		if len(strings.Split(par.Mountpoint, "/")) > 3 {
			continue
		}
		pars[size] = par
		size++
	}
	pars = pars[:size]
	log.Infof("  <Part> %v", pars)
}

func MonitorOs(intervalMs int, c *config.Config) {
	if diskFlag > 60 {
		diskFlag = 0
	}

	// 10秒监控一次CPU、内存、负载
	MonitorMemory(c)
	MonitorOsCpu(intervalMs, c)
	MonitorLoad(c)

	// 60秒监控一次磁盘容量
	// 30秒监控一次磁盘IO
	if diskFlag%6 == 0 {
		MonitorDisk(c)
		MonitorIO(c)
		log.Infoln()
	} else if diskFlag%3 == 0 {
		MonitorIO(c)
		log.Infoln()
	}

	diskFlag++
}

func MonitorLoad(c *config.Config) {
	v5, _ := load.Avg()
	log.Infof("  <Load> %v", v5)
	influx.WritesLoadPoints(v5)
	if c.Config.Monitor.EventOs.Load1Limit > 0 && v5.Load1 > float64(c.Config.Monitor.EventOs.Load1Limit) {
		RecordEventOsAlarm("Load1:>"+strconv.Itoa(c.Config.Monitor.EventOs.Load1Limit)+"|"+fmt.Sprintf("%.1f", v5.Load1), c)
	}
}

func MonitorMemory(c *config.Config) {
	vmem, _ := mem.VirtualMemory()
	// almost every return value is a struct
	log.Infof("  <Memory> Total: %v M, Free: %v M, UsedPercent: %f%% ", vmem.Total/1024/1024, vmem.Free/1024/1024, vmem.UsedPercent)
	influx.WritesMemPoints(vmem)
	if c.Config.Monitor.EventOs.MemLimit > 0 && vmem.UsedPercent > float64(c.Config.Monitor.EventOs.MemLimit) {
		RecordEventOsAlarm("Mem:>"+strconv.Itoa(c.Config.Monitor.EventOs.MemLimit)+"|"+fmt.Sprintf("%.1f", vmem.UsedPercent), c)
	}
}
func MonitorIO(c *config.Config) {
	// log.Infoln()
	// v6, _ := disk.IOCounters()
	// for _, par := range pars {
	// 	log.Infof("  <IO %s> %v", par.Mountpoint, v6[par.Mountpoint])
	// }
}
func MonitorDisk(c *config.Config) {
	log.Infoln()
	for _, par := range pars {
		vdhome, _ := disk.Usage(par.Mountpoint)
		log.Infof("  <Disk %s> Total: %v G, Free: %v G, UsedPercent: %f%% ", par.Mountpoint, vdhome.Total/1024/1024/1024, vdhome.Free/1024/1024/1024, vdhome.UsedPercent)
		influx.WritesDiskPoints(par, vdhome)
	}
}
func MonitorOsCpu(intervalMs int, c *config.Config) {
	go func() {
		vcpu, _ := cpu.Percent(time.Duration(intervalMs-2)*time.Second, false)
		log.Infof("  <CPU> Total: %f%%", vcpu[0])
		influx.WritesCpuPoints(vcpu)
		if c.Config.Monitor.EventOs.CpuLimit > 0 && vcpu[0] > float64(c.Config.Monitor.EventOs.CpuLimit) {
			RecordEventOsAlarm("CPU:>"+strconv.Itoa(c.Config.Monitor.EventOs.CpuLimit)+"|"+fmt.Sprintf("%.1f", vcpu[0]), c)
		}
	}()
}

func MonitorProcessWithChildren(proc *process.Process, name string, intervalMs int, c *config.Config) {
	go monitorProcessInner(proc, name, false, intervalMs, c)
	procs, err := proc.Children()
	if err != nil {
		return
	}
	for _, proc := range procs {
		go monitorProcessInner(proc, name, true, intervalMs, c)
	}
}

func MonitorProcess(pid int, name string, intervalMs int, c *config.Config) {
	proc, err := process.NewProcess(int32(pid))
	if err == nil {
		go monitorProcessInner(proc, name, false, intervalMs, c)
	} else {
		log.Infof("  can not get Process %v, err=%v", pid, err)
	}
}

func monitorProcessInner(proc *process.Process, name string, child bool, intervalMs int, c *config.Config) {
	cp := getCPUPercent(proc, intervalMs)

	enc := c.GetProgram(name)

	if enc.Program.EventProcess.CpuLimit > 0 && cp > float64(enc.Program.EventProcess.CpuLimit) {
		RecordEventProcessAlarm(name, "CPU:>"+strconv.Itoa(enc.Program.EventProcess.CpuLimit)+"|"+fmt.Sprintf("%.1f", cp), c)
	}

	me := getMemoryPercent(proc)
	if enc.Program.EventProcess.MemLimit > 0 && me > float32(enc.Program.EventProcess.MemLimit) {
		RecordEventProcessAlarm(name, "Mem:>"+strconv.Itoa(enc.Program.EventProcess.MemLimit)+"|"+fmt.Sprintf("%.1f", me), c)
	}

	if child {
		name = name + "_child"
	}
	log.Infof("  <Process> %v, %v, %v, %v, %v, %%%v, %%%v, %v",
		proc.Pid, name,
		newObj(proc.Username()),
		newObj(proc.Parent()),
		getChildren(proc),
		cp,
		me,
		newObj(proc.IOCounters()),
	)
	influx.WriteProcessPoint(name, cp, me, proc)
	//log.Infof("cmd: %v", newObj(proc.Cmdline()))
	log.Infoln()
}

func getChildren(proc *process.Process) []int32 {
	rr, _ := proc.Children()
	ret := make([]int32, len(rr))
	for i, r := range rr {
		ret[i] = r.Pid
	}
	return ret
}

func getCPUPercent(proc *process.Process, intervalMs int) float64 {
	// cp, _ := proc.CPUPercent()
	// return cp
	cp, _ := proc.Percent(time.Duration(intervalMs-2) * time.Second)
	return cp
}

func getMemoryPercent(proc *process.Process) float32 {
	cp, _ := proc.MemoryPercent()
	return cp
}
